//==============================================================================
//
//           DBZipFile - the bzip2 file class in the ofc-library
//
//               Copyright (C) 2005  Dick van Oudheusden
//  
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
//
//==============================================================================
// 
//  $Date: 2006-07-22 13:28:55 $ $Revision: 1.8 $
//
//==============================================================================

#include "ofc/DBZipFile.h"

#include <errno.h>
#include <unistd.h>
#include <string.h>


#ifdef HAVE_DBZIPFILE
#include <bzlib.h>
#endif

#include "ofc/DDatable.h"


#if _INTERFACE_

#include "ofc/config.h"

#include "ofc/DText.h"
#include "ofc/DData.h"
#include "ofc/DList.h"
#include "ofc/DTextReadable.h"
#include "ofc/DTextWritable.h"
#include "ofc/DDataReadable.h"
#include "ofc/DDataWritable.h"

#if defined(HAVE_LIBBZ2) && defined(HAVE_BZLIB_H)
#define HAVE_DBZIPFILE 1
#endif

#ifdef HAVE_DBZIPFILE

//
// The DBZipFile class implements a number of methods for opening of, writing to, 
// reading from and closing of bzip2 files. See the open method for used settings for 
// the bzip2 library.
//

@interface DBZipFile : Object <DTextReadable,DTextWritable,DDataReadable,DDataWritable>
{
@private
  void           *_file;    // the file pointer
  void           *_bzfile;  // the bzip file pointer
  BOOL            _read;    // is the file opened for reading ?
  BOOL            _eof;     // is end of file reached
  int             _error;   // the reported error
}

#endif
#endif



#ifdef HAVE_DBZIPFILE

@implementation DBZipFile


//// Constructors

//
// Initialise an empty file object
//
// @return the object
//

- (DBZipFile *) init
{
  [super init];

  _file   = NULL;
  _bzfile = NULL;
  _read   = YES;
  _eof    = NO;

  return self;
}

//
// Open a bzip file with default level and strategy
//
// @param name     the filename (with extension .bz2)
// @param mode     the file mode (r,w)
//
// @return the object
//

- (DBZipFile *) init :(const char *) name :(const char *) mode
{
  [self init];

  [self open :name :mode :NO];
  
  return self;
}

//
// Open a bzip file
//
// @param name      the filename (with extension .bz2)
// @param mode      the file mode (r,w)
// @param small     should a small memory footprint be used
//
// @return the object
//

- (DBZipFile *) init :(const char *) name :(const char *) mode :(BOOL) small
{
  [self init];

  [self open :name :mode :small];
  
  return self;
}


//// Copy related methods

//
// Do a shallow copy of the object (not implemented)
//
// @return the object
//
- shallowCopy
{
  WARNING(DW_METHOD_NOT_IMPL, "shallowCopy");

  return [super shallowCopy];
}


//// Deconstructor

//
// Free the object
//
// @return the file object
//

- free
{
  if (_file != NULL)
  {
    [self close];
  }
  
  [super free];

  return self;
}


//// File info methods

//
// Check if the file is open
//
// @return is the file open ?
//

- (BOOL) isOpen
{
  return (_file != NULL);
}

//
// Return the last error
//
// @return the last error
//

- (int) error
{
  return _error;
}


//// File opening methods

//
// Open a bzip file with big memory usage
//
// @param name     the filename (with extension .bz2)
// @param mode     the file mode (r,w)
//
// @return is the file open ?
//

- (BOOL) open :(const char *) name :(const char *) mode
{
  return([self open :name :mode :NO]);
}

//
// Open a bzip file (note: the defaults for the bzip2 library: verbosity = 0 (silent), workFactor = 0,
// blockSize100k = 5 (for small) and 9 (for not small)
//
// @param name      the filename (with extension .bz2)
// @param mode      the file mode (r,w)
// @param small     should a small memory footprint be used ?
//
// @return is the file open ?
//

- (BOOL) open :(const char *) name :(const char *) mode :(BOOL) small
{
  BOOL ok = NO;
  
  if ((name == NULL) || (*name == EOS))
  {
    WARNING(DW_INVALID_ARG, "name");
  }
  else if ((mode == NULL) || ((*mode != 'r') && (*mode != 'w')))
  {
    WARNING(DW_INVALID_ARG, "mode");
  }
  else
  {
    if (_file != NULL)
    {
      [self close];
    }
  
    if (*mode == 'r')
    {
      _file = fopen(name, "rb");
  
      if (_file != NULL)
      {
        _eof = NO;
      
        _bzfile = BZ2_bzReadOpen(&_error, _file, 0, small, NULL, 0);
        
        _read = YES;
        
        ok = (_error == BZ_OK);
      }
      else
        _error = errno;
    }
    else // *mode == 'w'
    {
      _file = fopen(name, "wb");
      
      if (_file != NULL)
      {
        _eof = NO;
        
        _bzfile = BZ2_bzWriteOpen(&_error, _file, (small ? 5 : 9), 0, 0);
        
        _read = NO;
        
        ok = (_error == BZ_OK);
      }
      else 
        _error = errno;
    }
  }
  
  return (ok);
}


//// TextReadable protocol implementation

//
// Read a text string (all available text)
//
// @return a (new) text string
//

- (DText *) readText
{
  DText *string = [DText new];

  if ((_file != NULL) && (_read))
  {
    const
    int   length = 2048;
    char  buffer[length];
    int   result;

    _error = (_eof ? BZ_STREAM_END : BZ_OK);
    
    while (_error == BZ_OK)
    {
      result = BZ2_bzRead(&_error, _bzfile, buffer, length-1);
      
      if (((_error == BZ_OK) || (_error == BZ_STREAM_END)) && (result > 0))
      {
        buffer[result] = EOS;
        
        [string append :buffer];
      }
      _eof = (_error == BZ_STREAM_END);
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
    
  return string;
}

//
// Read a text string 
//
// @param length   the length of the text to be read
//
// @return a (new) text string
//

- (DText *) readText :(long) length
{
  DText *string = [DText alloc];

  [string init];

  if (length > 0)
  {
    if ((_file != NULL) && (_read))
    {
      const int      size = 2048;
      unsigned char  buffer[size];
      int            result;
      long           total = 0;
      long           part  = size;
    
      _error = (_eof ? BZ_STREAM_END : BZ_OK);
      
      while ((_error == BZ_OK) && (total < size))
      {
        if ((length - total) < part)
        {
          part = length - total - 1;
        }

        result = BZ2_bzRead(&_error, _bzfile, buffer, size);

        if ((_error == BZ_OK) || (_error == BZ_STREAM_END))
        {
          [string append :(const char *) buffer];

          total += result;
          
          _eof = (_error == BZ_STREAM_END);
        }
      }
    }
    else
      WARNING(DW_OBJECT_NOT_INIT, "open");
  }

  return string;
}  

//
// Read a line of text (till Eof or \n)
//
// @return a (new) text string (or nil for no data)
//

- (DText *) readLine;
{
  DText *string = nil;
  
  if ((_file != NULL) && (_read))
  {
    if (!_eof)
    {
      char ch;
      int  result;
      
      string = [DText alloc];

      [string init];

      do
      {
        result = BZ2_bzRead(&_error, _bzfile, &ch, sizeof(char));

        if (_error == BZ_OK)
        {
          if ((ch != '\n') && (ch != '\r'))
          {
            [string push :ch];
          }
        }
        else if (_error == BZ_STREAM_END)
        {
          _eof = YES;
        }
      }
      while ((_error == BZ_OK) && (ch != '\n'));
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return string;
}

//
// Read a character
//
// @return the character read (EOS for EOF)
//

- (char) readChar;
{
  char ch = EOS;

  if ((_file != NULL) && (_read))
  {
    if (!_eof)
    {
      int result;
    
      result = BZ2_bzRead(&_error, _bzfile, &ch, sizeof(ch));
    
      if (_error == BZ_STREAM_END)
      {
        ch = EOS;
        
        _eof = YES;
      }
      else if (_error != BZ_OK)
      {
        ch = EOS;
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ch;
}

//
// Tell the current position in the readable
//
// @return -1 (not implemented)
//

- (unsigned long) tell;
{
  return -1;
}

//
// Move the current position in the readable
//
// @param offset   the offset from the origin (in bytes)
// @param origin   the origin for the offset 
//
// @return NO (not implemented)
//

- (BOOL) seek :(unsigned long) offset :(int) origin
{
  return NO;
}

//
// Skip a number of positions
//
// @param offset   the number of bytes to skip
//
// @return NO (not implemented)
//

- (BOOL) skip :(unsigned long) offset
{
  return NO;
}

//
// Check if the end of the bzip2 file is reached
//
// @return is it?
//

- (BOOL) isEof
{
  return _eof;
}


//// Implementation of DTextWritable

//
// Write text string 
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeText :(const char *) text
{
  BOOL ok = NO;

  if (text == NULL)
  {
    WARNING(DW_INVALID_ARG, "text");
  }
  else if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, (void *) text, strlen(text));
    
    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return ok;
}

//
// Write line (appending a '\n')
//
// @param text     the text to be written
//
// @return success
//

- (BOOL) writeLine :(const char *) text
{
  BOOL ok = NO;
  
  if (text == NULL)
  {
    WARNING(DW_INVALID_ARG, "text");
  }
  else if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, (void *) text, strlen(text));
    
    if (_error == BZ_OK)
    {
#ifdef WIN32
      char eofl[] = "\r\n";
#else
      char eofl[] = "\n";
#endif
      
      BZ2_bzWrite(&_error, _bzfile, eofl, strlen(eofl));
      
      ok = (_error == BZ_OK);
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
    
  return ok;
}

//
// Write a character
//
// @param ch       the character to be written
//
// @return success
//

- (BOOL) writeChar :(char) ch
{
  BOOL ok = NO;
  
  if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, &ch, sizeof(ch));
    
    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return ok;
}


//// Methods for DDataReadable protocol

//
// Read a data string 
//
// @param length   the length of the data
//
// @return a (new) data string object
//

- (DData *) readData :(unsigned long) length
{
  DData *string = [DData alloc];

  [string init];
  
  if (length > 0)
  {
    if ((_file != NULL) && (_read))
    {
      const int     size  = 2048;
      unsigned char buffer[size];
      unsigned long total = 0;
      unsigned long part  = size;
      int           result;

      _error = (_eof ? BZ_STREAM_END : BZ_OK);
      
      while ((_error == BZ_OK) && (total < length))
      {
        if ((length - total) < part)
        {
          part = length - total;
        }
      
        result = BZ2_bzRead(&_error, _bzfile, buffer, sizeof(buffer[0]) * part);
        
        if ((_error == BZ_OK) || (_error == BZ_STREAM_END))
        {
          [string append :buffer :result];

          total += result;
          
          _eof = (_error == BZ_STREAM_END);
        }
      }
    }
    else
      WARNING(DW_OBJECT_NOT_INIT, "open");
  }

  return string;
}

//
// Read a byte
//
// @return the byte read (0 for error)
//

- (unsigned char) readByte
{
  unsigned char ch = 0;

  if ((_file != NULL) && (_read))
  {
    if (!_eof)
    {
      int result;
      
      result = BZ2_bzRead(&_error, _bzfile, &ch, sizeof(ch));
      
      if (_error == BZ_STREAM_END)
      {
        ch = 0;
        
        _eof = YES;
      }
      else if (_error != BZ_OK)
      {
        ch = 0;
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ch;
}

//
// Read a short
//
// @return the short
//

- (short) readShort
{
  short nr = 0;

  if ((_file != NULL) && (_read))
  {
    if (!_eof)
    {
      int result;
      
      result = BZ2_bzRead(&_error, _bzfile, &nr, sizeof(nr));
      
      if (_error == BZ_STREAM_END)
      {
        nr = 0;
        
        _eof = YES;
      }
      else if (_error != BZ_OK)
      {
        nr = 0;
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return nr;
}

//
// Read a long
//
// @return the long
//

- (long) readLong;
{
  long nr = 0;

  if ((_file != NULL) && (_read))
  {
    if (!_eof)
    {
      int result;
      
      result = BZ2_bzRead(&_error, _bzfile, &nr, sizeof(nr));
      
      if (_error == BZ_STREAM_END)
      {
        nr = 0;
        
        _eof = YES;
      }
      else if (_error != BZ_OK)
      {
        nr = 0;
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return nr;
}

//
// Read a double
//
// @return the double
//

- (double) readDouble;
{
  double nr = 0.0;

  if ((_file != NULL) && (_read))
  {
    if (!_eof)
    {
      int result;
      
      result = BZ2_bzRead(&_error, _bzfile, &nr, sizeof(nr));
      
      if (_error == BZ_STREAM_END)
      {
        nr = 0.0;
        
        _eof = YES;
      }
      else if (_error != BZ_OK)
      {
        nr = 0.0;
      }
    }
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return nr;
}


//// DDataWritable protocol implementation

//
// Write a data string 
//
// @param data     the data to be written
// @param length   the length of the data
//
// @return success
//

- (BOOL) writeData :(const unsigned char *) data :(unsigned long) length;
{
  BOOL ok = NO;

  if (data == NULL)
  {
    WARNING(DW_INVALID_ARG, "text");
  }
  else if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, (void *) data, length);
    
    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ok;
}
  
//
// Write a byte
//
// @param byte     the byte to be written
//
// @return success
//

- (BOOL) writeByte :(unsigned char) byte;
{
  BOOL ok = NO;
  
  if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, &byte, sizeof(byte));
    
    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return ok;
}
 
//
// Write a short 
//
// @param nr       the short to be written
//
// @return success
//

- (BOOL) writeShort :(short) nr;
{
  BOOL ok = NO;
  
  if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, &nr, sizeof(nr));
    
    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ok;
}
 
//
// Write a long
//
// @param nr       the long to be written
//
// @return success
//

- (BOOL) writeLong :(long) nr
{
  BOOL ok = NO;
  
  if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, &nr, sizeof(nr));

    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");
  
  return ok;
}
 
//
// Write a double
//
// @param nr       the double to be written
//
// @return success
//

- (BOOL) writeDouble :(double) nr
{
  BOOL ok = NO;
  
  if ((_file != NULL) && (!_read))
  {
    BZ2_bzWrite(&_error, _bzfile, &nr, sizeof(nr));
    
    ok = (_error == BZ_OK);
  }
  else
    WARNING(DW_OBJECT_NOT_INIT, "open");

  return ok;
}


//// List related methods

//
// Read all lines from a file into a list of strings
//
// @return a (new) list of (new) DText objects
//

- (DList *) readLines
{
  DList   *list = [DList alloc];
 
  [list init];
 
  if (_file != NULL)
  {
    while (!_eof)
    {
      DText *line = [self readLine];

      if (line != nil)
      {
        [list append :line];
      }
    }
  }

  return list;
}

//
// Writes all the elements ('lines') of text from a list to file
//
// @param list     the list with objects that responds to textable protocol
//
// @return success
//

- (BOOL) writeLines :(DList *) list
{
  BOOL success = YES;
  
  if (list != NULL)
  {
    DListIterator *iter  = [DListIterator alloc];
    id             object;

    [iter init :list];

    object = [iter first];            
    while ((object != nil) && (success))
    {
      if ([object conformsTo :@protocol(DTextable)])
      {
        DText *string = [object toText];
  
        success = [self writeLine :[string cstring]];

        [string free];
      }
      
      object = [iter next];
    }
    
    [iter free];
  }
  
  return success;
}


//// File manipulation

//
// Flush the output buffers of the file 
//
// @return NO (not implemented for BZipFile)
//

- (BOOL) flush
{
  return (NO);
}


//// File closing methods

//
// Close the file
//
// @return the file object
//

- (DBZipFile *) close
{
  if (_file != NULL)
  {
    if (_read)
    {
      BZ2_bzReadClose(&_error, _bzfile);
    }
    else
    {
      BZ2_bzWriteClose(&_error, _bzfile, NO, NULL, NULL);
    }
    _bzfile = NULL;
    
    fclose(_file);
    
    _file = NULL;
  }

  return self;
}

@end

#endif

/*===========================================================================*/

